package com.green.dao.mybatis;

import java.lang.reflect.Field;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.apache.log4j.Logger;
import org.springframework.core.OrderComparator;
import org.springframework.core.Ordered;
import org.springframework.util.ReflectionUtils;

import com.green.dao.mybatis.dialect.Dialect;


/**
 * 分页拦截器, 支持多种数据库
 * 
 */
@Intercepts({@Signature(
		type= Executor.class,
		method = "query",
		args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})})
public class PaginationInterceptor extends AbstractInterceptor {
	private final static ProcessInterceptAfterProcessManager processInterceptAfterProcessManager=new ProcessInterceptAfterProcessManager();
	private final static Logger logger = Logger.getLogger(PaginationInterceptor.class);
	private static int MAPPED_STATEMENT_INDEX = 0;
	private static int PARAMETER_INDEX = 1;
	private static int ROWBOUNDS_INDEX = 2;

	private Dialect dialect;

	@Override
	public Object intercept(Invocation invocation) throws Throwable {
		processIntercept(invocation.getArgs());
		return invocation.proceed();
	}

	@SuppressWarnings({ "rawtypes" })
	private void processIntercept(final Object[] queryArgs) {
		MappedStatement ms = (MappedStatement) queryArgs[MAPPED_STATEMENT_INDEX];
		Object parameter = queryArgs[PARAMETER_INDEX];
		final RowBounds rowBounds = (RowBounds) queryArgs[ROWBOUNDS_INDEX];
		int offset = rowBounds.getOffset();
		int limit = rowBounds.getLimit();
		
		this.setCu(parameter);

		//分页
		if (dialect.supportsLimit() && (offset != RowBounds.NO_ROW_OFFSET || limit != RowBounds.NO_ROW_LIMIT)) {
			BoundSql boundSql = ms.getBoundSql(parameter);
			String sql = boundSql.getSql().trim();
			if(parameter instanceof Map){
				Map params = (Map) parameter;
				if (params.containsKey("sidx")) {
					sql = attachSorts(sql, (String[]) params.get("sidx"),
							(String) params.get("sord"));
				}
			}
			if (dialect.supportsLimitOffset()) {
				sql = dialect.getLimitString(sql, offset, limit);
				offset = RowBounds.NO_ROW_OFFSET;
			} else {
				sql = dialect.getLimitString(sql, 0, limit);
			}
			limit = RowBounds.NO_ROW_LIMIT;

			queryArgs[ROWBOUNDS_INDEX] = new RowBounds(offset, limit);
			BoundSql newBoundSql =this.getNewSqlBoundSql(ms, sql, boundSql);  
				//new BoundSql(ms.getConfiguration(), sql, boundSql.getParameterMappings(), boundSql.getParameterObject());
			MappedStatement newMs = copyFromMappedStatement(ms, new BoundSqlSqlSource(newBoundSql), false);
			queryArgs[MAPPED_STATEMENT_INDEX] = newMs;
			if(logger.isDebugEnabled()) {
				logger.debug("sql = \n" + sql);
			}
		} else if(parameter instanceof CountParameter) {
			//获取总数
			parameter = ((CountParameter) parameter).getParameter();
			//this.setCu(parameter);
			BoundSql boundSql = ms.getBoundSql(parameter);
			//String sql = boundSql.getSql().trim().toLowerCase();
			String sql = boundSql.getSql().trim();
			sql = "select count(1) from (" + sql + ") tmp";
			BoundSql newBoundSql =this.getNewSqlBoundSql(ms, sql, boundSql); 
				//new BoundSql(ms.getConfiguration(), sql, boundSql.getParameterMappings(), boundSql.getParameterObject());
			
			
			MappedStatement newMs = copyFromMappedStatement(ms, new BoundSqlSqlSource(newBoundSql), true);
			queryArgs[MAPPED_STATEMENT_INDEX] = newMs;
			queryArgs[PARAMETER_INDEX] = parameter;
			if(logger.isDebugEnabled()) {
				logger.debug("sql = \n" + sql);
			}
		}
			else{
			BoundSql boundSql = ms.getBoundSql(parameter);
			String sql = boundSql.getSql().trim();
			if (parameter instanceof Map) {
				Map params = (Map) parameter;
				if (params.containsKey("sidx")) {
					sql = attachSorts(sql, (String[]) params.get("sidx"),
							(String) params.get("sord"));
				}
			}
			logger.debug(sql);
		}
		
		this.runProcessInterceptAfterProcess(dialect,(MappedStatement) queryArgs[MAPPED_STATEMENT_INDEX], parameter);
	}

	/**
	 * 
	 * intro: attach 'order by' to sql
	 * 
	 * @author Lineshow
	 * @since 2012-8-3
	 */
	private String attachSorts(String sql, String[] sidxs, String sord) {
		Pattern pattern = Pattern.compile("order(\\s)+by");
		Matcher matcher = pattern.matcher(sql);
		if (matcher.find()) {
			return sql;
		}
		return new StringBuilder(sql).append(" order by ")
				.append(org.apache.commons.lang.StringUtils.join(sidxs))
				.append(" ")
				.append(org.apache.commons.lang.StringUtils.trimToEmpty(sord))
				.toString();
	}

	@Override
	public Object plugin(Object target) {
		return Plugin.wrap(target, this);
	}

	@Override
	public void setProperties(Properties properties) {
		String dialectClass = properties.getProperty("dialectClass");
		try {
			dialect = (Dialect) Class.forName(dialectClass).newInstance();
		} catch (Exception e) {
			throw new RuntimeException(
					"cannot create dialect instance by dialectClass:"
							+ dialectClass, e);
		}
	}
	
	/**
	 * 反射替换sql 主要处理additionalParameters生成的参数
	 * @param sql
	 * @param newBoundSql
	 */
	private BoundSql getNewSqlBoundSql(MappedStatement ms,String sql,BoundSql boundSql){
		try {
			final Field field=ReflectionUtils.findField(boundSql.getClass(), "sql");
			ReflectionUtils.makeAccessible(field);
			ReflectionUtils.setField(field, boundSql, sql);
			return boundSql;
		}catch (Exception e) {
			logger.warn("反射设置sql失败"+e.getMessage(),e); 
		}
		return  new BoundSql(ms.getConfiguration(), sql, boundSql.getParameterMappings(), boundSql.getParameterObject());
	}
	/**
	 * 填充cu
	 * @param parameter
	 */
	private void setCu(Object parameter){
//		if(parameter==null){
//			return;
//		}
//		final Person person= SessionPerson.get();
//		if(person==null){
//			return;
//		}
//		final Org cu=person.getControUnit();
//		if(cu==null||cu.getId()==null){
//			return;
//		}
//		if(parameter instanceof Map){
//			final Map temp=(Map) parameter;
//			if(temp.containsKey("cu")){
//				temp.remove("cu");			
//			}
////			temp.put("cu", cu.getId());
//		}
//		if(parameter instanceof Entity){
//		   final Entity entity=(Entity) parameter;
//		   if(null==entity.getCu()){
//			   //entity.setCu(cu);
//		   }else{
//			   entity.setCu(null);
//		   }
//		}
//		if(parameter instanceof CountParameter){
//			final Object obj=((CountParameter) parameter).getParameter();
//			if(obj instanceof Map && ((Map) obj).containsKey("cu")){
//				((Map)obj).remove("cu");
//			}
//		}
	}
	
	private void runProcessInterceptAfterProcess(Dialect dialect,MappedStatement mappedStatement,Object parameter){
		final Collection<ProcessInterceptAfterProcess> list=processInterceptAfterProcessManager.getProcessInterceptAfterProcesses();
		if(list==null){
			return;
		}
		for (ProcessInterceptAfterProcess processInterceptAfterProcess : list) {
			processInterceptAfterProcess.process(dialect, mappedStatement, parameter);
		}
	}

	public static class CountParameter {
		private final Object parameter;
		public CountParameter(Object parameter) {
			this.parameter = parameter;
		}
		public Object getParameter() {
			return parameter;
		}
	}
	
	public static  ProcessInterceptAfterProcessManager getProcessInterceptAfterProcessManager(){
		return processInterceptAfterProcessManager;
	}
	
	public static class ProcessInterceptAfterProcessManager{
		//private ConcurrentLinkedQueue<ProcessInterceptAfterProcess> processInterceptAfterProcesses=new ConcurrentLinkedQueue();
		private final static ThreadLocal<List<ProcessInterceptAfterProcess>> processInterceptAfterProcessesHold=new ThreadLocal<List<ProcessInterceptAfterProcess>>();
		public Collection<ProcessInterceptAfterProcess> getProcessInterceptAfterProcesses(){
			final List<ProcessInterceptAfterProcess> list=processInterceptAfterProcessesHold.get();
		     if(list==null||list.isEmpty()){
		    	 return null;
		     }
			OrderComparator.sort(list);
			return Collections.unmodifiableCollection(list);
		}
		
		public void addProcessInterceptAfterProcess(ProcessInterceptAfterProcess processInterceptAfterProcess){
			List<ProcessInterceptAfterProcess> list=processInterceptAfterProcessesHold.get();
			if(list==null){
				list=new LinkedList<PaginationInterceptor.ProcessInterceptAfterProcess>();
				processInterceptAfterProcessesHold.set(list);
			}
			list.add(processInterceptAfterProcess);
		}
		
		public void removeProcessInterceptAfterProcess(ProcessInterceptAfterProcess processInterceptAfterProcess){
			final List<ProcessInterceptAfterProcess> list=processInterceptAfterProcessesHold.get();
			if(list==null){
				return;
			}
			list.remove(processInterceptAfterProcess);
		}
	}
	
	public static interface ProcessInterceptAfterProcess extends Ordered{
		void process(Dialect dialect,MappedStatement mappedStatement,Object parameter);
	}

}
